/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.skimmed;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantCP;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantFieldref;
import org.apache.bcel.classfile.ConstantInterfaceMethodref;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;

import milk.jpatch.classLevel.FieldAdd;
import milk.jpatch.classLevel.FieldPatch;
import milk.jpatch.classLevel.MethodAdd;
import milk.jpatch.classLevel.MethodPatch;
import milk.jpatch.fileLevel.ClassPatch;
import milk.skimmed.Key.KeyType;

/**
 * Hunts for keys to identify jarps as requirements.
 * 
 * Basically, unique parts of code are searched for and resolved as a
 * requirement.
 * @author Thomas Kerber
 * @version 1.0.0
 */
public class KeyHunter{
    
    /**
     * Resolves associated jarps for a mod.
     * @param modRoot The mod to resolve jarps for.
     * @return The requirement jarps.
     * @throws IOException
     */
    public static Set<Jarp> resolveAssocs(File modRoot) throws IOException{
        // TODO: mebbe store keys in between or so depends on how long
        // generation takes.
        return resolveAssocs(modRoot, genKeys(Skimmed.CONFIG.getJarpList()));
    }
    
    /**
     * Resolves associated jarps for a mod.
     * @param modRoot The mod to resolve jarps for.
     * @param keys The keys to use for the resolution.
     * @return The requirement jarps.
     * @throws IOException
     */
    public static Set<Jarp> resolveAssocs(File modRoot, List<Key> keys)
            throws IOException{
        Set<Jarp> ret = new HashSet<Jarp>();
        for(File f : modRoot.listFiles()){
            if(f.isFile() && f.getName().toLowerCase().endsWith(".class")){
                ret.addAll(resolveAssocs(
                        new ClassParser(f.getAbsolutePath()).parse().
                                getConstantPool(),
                        keys));
            }
        }
        return ret;
    }
    
    /**
     * Resolves associated jarps for a constant pool.
     * @param cp The constant pool to resolve jarps for.
     * @param keys The keys to use for the resolution.
     * @return The requirement jarps.
     */
    public static Set<Jarp> resolveAssocs(ConstantPool cp, List<Key> keys){
        Set<Jarp> ret = new HashSet<Jarp>();
        for(Constant c : cp.getConstantPool()){
            if(c instanceof ConstantClass){
                String name = ((ConstantClass)c).getBytes(cp);
                for(Key key : keys){
                    if(key.matches(name, KeyType.CLASS)){
                        ret.add(key.assoc);
                        break;
                    }
                }
            }
            else if(c instanceof ConstantFieldref){
                ConstantFieldref fieldC = (ConstantFieldref)c;
                String path = fieldC.getClass(cp) + "." +
                        ((ConstantNameAndType)cp.getConstant(
                                fieldC.getNameAndTypeIndex())).getName(cp);
                for(Key key : keys){
                    if(key.matches(path, KeyType.FIELD)){
                        ret.add(key.assoc);
                        break;
                    }
                }
            }
            else if(c instanceof ConstantMethodref ||
                    c instanceof ConstantInterfaceMethodref){
                ConstantCP methodC = (ConstantCP)c;
                String path = methodC.getClass(cp) + "." +
                            milk.jpatch.Util.getMethodIdentifier(methodC, cp);
                for(Key key : keys){
                    if(key.matches(path, KeyType.METHOD)){
                        ret.add(key.assoc);
                        break;
                    }
                }
            }
        }
        return ret;
    }
    
    /**
     * Generates keys.
     * @param jarps The jarps to generate keys from.
     * @return The keys generated.
     */
    public static List<Key> genKeys(List<Jarp> jarps){
        List<Key> ret = new ArrayList<Key>();
        for(Jarp jarp : jarps){
            try{
                File extractedJarp =
                        milk.jpatch.Util.extractZip(jarp.location);
                ret.addAll(findKeysInJarp(jarp, extractedJarp, ""));
                // TODO: mebbe I forgot to delete alot of tmp st00f.
                milk.jpatch.Util.remDir(extractedJarp);
            }
            catch(Exception e){
                Util.logger.log(
                        Level.WARNING,
                        "Failed to find keys for jarp '" + jarp.getName() +
                                "'.",
                        e);
            }
        }
        return ret;
    }
    
    /**
     * Finds keys in a jarp.
     * @param assoc The jarp to associate to the keys.
     * @param extractedJarp The extracted jarp.
     * @param currpath The current path for key names.
     * @return The keys contained in the jarp.
     * @throws IOException
     */
    public static List<Key> findKeysInJarp(Jarp assoc, File extractedJarp,
            String currpath) throws IOException{
        List<Key> ret = new ArrayList<Key>();
        for(File f : extractedJarp.listFiles()){
            if(f.isFile()){
                if(f.getName().toLowerCase().endsWith(".class")){
                    ret.add(new Key(
                            assoc,
                            currpath + "." + f.getName().
                                    substring(0, f.getName().length() - 6),
                            KeyType.CLASS));
                }
                else if(f.getName().toLowerCase().endsWith(
                        ClassPatch.POSTFIX)){
                    ClassPatch cp = ClassPatch.deserializeAt(null,
                            f.getAbsoluteFile());
                    ret.addAll(findKeysInJCP(
                            assoc,
                            cp,
                            currpath + "." + f.getName().substring(
                                    f.getName().length() -
                                    ClassPatch.POSTFIX.length())));
                }
            }
            else if(f.isDirectory()){
                ret.addAll(findKeysInJarp(
                        assoc,
                        extractedJarp,
                        currpath + "." + f.getName()));
            }
        }
        
        return ret;
    }
    
    /**
     * Finds keys in a jcp.
     * @param assoc The associated jarp.
     * @param cp The JCP.
     * @param currpath The current path for the key name.
     * @return The keys contained in the JCP.
     */
    public static List<Key> findKeysInJCP(Jarp assoc, ClassPatch cp,
            String currpath){
        List<Key> ret = new ArrayList<Key>();
        for(FieldPatch fp : cp.getFieldsPatch().getPatches()){
            if(fp instanceof FieldAdd)
                ret.add(new Key(assoc, currpath + "."  + fp.getName(),
                        KeyType.FIELD));
        }
        for(MethodPatch mp : cp.getMethodsPatch().getPatches()){
            if(mp instanceof MethodAdd)
                ret.add(new Key(assoc, currpath + "." + mp.getIdentifier(),
                        KeyType.METHOD));
        }
        return ret;
    }
    
}
